package com.jphenix.driver.regc;

//#region 【引用区】
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

import com.jphenix.driver.cluster.ClusterFilter;
import com.jphenix.driver.cluster.IClusterTrigger;
import com.jphenix.driver.cluster.ServerInfoVO;
import com.jphenix.driver.json.Json;
import com.jphenix.driver.regc.vo.ActionVO;
import com.jphenix.driver.regc.vo.ContextVO;
import com.jphenix.driver.threadpool.ThreadSession;
import com.jphenix.kernel.objectloader.interfaceclass.IBeanFactoryManager;
import com.jphenix.kernel.objectloader.interfaceclass.IBeanRegister;
import com.jphenix.kernel.objectloader.interfaceclass.IBeanRegisterChild;
import com.jphenix.kernel.script.ScriptLoader;
import com.jphenix.kernel.script.ScriptVO;
import com.jphenix.servlet.common.ActionContext;
import com.jphenix.servlet.filter.BaseFilter;
import com.jphenix.servlet.parent.ServiceBeanParent;
import com.jphenix.share.lang.SDate;
import com.jphenix.share.printstream.PrintWriterTool;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.share.util.StringUtil;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.docs.Running;
import com.jphenix.standard.exceptions.FixException;
import com.jphenix.standard.script.IScriptBean;
import com.jphenix.standard.servlet.IActionBean;
import com.jphenix.standard.servlet.IActionContext;
import com.jphenix.standard.servlet.IFilter;
import com.jphenix.standard.servlet.IRequestManager;
import com.jphenix.standard.servlet.IResponseManager;
import com.jphenix.standard.servlet.api.IFilterConfig;
import com.jphenix.standard.servlet.api.IRequest;
import com.jphenix.standard.servlet.api.IResponse;
import com.jphenix.standard.viewhandler.IViewHandler;
//#endregion

//#region 【类说明区】
/**
 * 注册中心过滤器
 * com.jphenix.driver.regc.RegistCenterFilter
 * 
 * 规则：
 * 1. 本地注册的动作类，没有虚拟路径(只有本地注册动作类的虚拟路径为空)
 * 2. 本地注册全部完毕后，或者发生变化时，只将本地注册的信息推送给注册中心数据主服务器，并不做广播。由注册中心主服务器统一广播
 * 
 * 2021-04-27 去掉了不匹配动作检测容器，因为会出现开始不存在该动作，随后就存在了。如果放在不匹配容器中，随后一直不匹配
 * 2022-09-04 隔离了ServletApi，兼容新老Tomcat
 * 2022-09-05 修改了发现的错误
 * 2022-10-20 设置为默认禁用
 * 2022-10-21 优化了代码
 * 2023-04-03 去掉了扩展库功能，在当前类中出去了代理脚本外壳类相关代码
 * 2024-07-25 取消了是否禁用并发控制（太消耗系统资源，已取消）
 * 
 * @author MBG
 * 2021年03月10日
 */
//#endregion
@Running({"90"})
@ClassInfo({"2024-07-25 12:39","注册中心过滤器"})
@BeanInfo({"regc"})
public class RegistCenterFilter extends ServiceBeanParent implements IFilter,IClusterTrigger,IBeanRegister {

	//#region 【声明区】
	private   String         actionName         = null;              // 响应内部指令处理动作名 (从配置文件中获取值)
	private   String         localActionExtName = null;              // 本地动作扩展名
	private   boolean        enabledBeanReg     = true;              // 是否允许本地动作类直接注册使用 (从配置文件中获取值)
	protected IFilterConfig  config             = null;              // 过滤器配置信息处理类
	private   boolean        disabledCluster    = true;              // 是否禁用集群
	private   ClusterFilter  cluster            = null;              // 集群管理类
	//private   String         sessionCookieName  = "JSESSIONID";    // 在Cookie中保存的会话信息主键名
	private   String         loopServerId       = null;              // 回路服务器ID
	//private   String         callLoopServerId   = null;            // 访问回路服务器需要经过的中间服务器主键
	//private   int            loopServerLevel    = -1;              // 回路中转层级
	private   String         localContextPath   = null;              // 本地虚拟巨鲸（从配置文件中获取）
	private   String[] allowOriginVals          = null;              // 允许跨站的域名数组，*为全部域名
	private   ScriptLoader   sl                 = null;              // 脚本加载器
	private   Map<String,ContextVO> ctxMap      = new HashMap<>();   // 动作对照容器
	private   ContextVO localContextVO          = null;              // 本地上下文信息容器
	private   BaseFilter     baseFilter         = null;              // 主过滤器
	//private   Map<String,String> noMatchingMap  = new HashMap<>();   //不匹配的URL容器 废弃该功能，因为会存在这种情况，之前没这个动作，随后就有了，结果被这个容器给屏蔽掉了
	//#endregion

	//#region clusterInited(caller) 【事件】由ClusterFilter调用，集群服务加载完毕后调用该方法
	/**
	 * 【事件】由ClusterFilter调用，集群服务加载完毕后调用该方法
	 * 2021年03月10日
	 */
	@Override
	public void clusterInited(ClusterFilter caller) {
		cluster = caller;
		if(!cluster.disabled()) {
			disabledCluster = false;
		}
		//本机配置信息
		ServerInfoVO localVO = cluster.getLocalServerInfoVO();
		//在Cookie中保存的会话信息主键名
		String value = localVO.parameter("session_cookie_name");
		if(value.length()>0) {
			//sessionCookieName = value;
		}
	}
	//#endregion

	//#region validSended(siVO) 【事件】由ClusterFilter调用，首次完成发送心跳到某个目标服务器后执行该方法
	/**
	 * 【事件】由ClusterFilter调用，首次完成发送心跳到某个目标服务器后执行该方法
	 * @param siVO   目标服务器信息类
	 * 2021年03月10日
	 * @author MBG
	 */
	@Override
	public void validSended(ServerInfoVO siVO) {
		//TODO 待完善
		//返回报文头
		Map<String,String> header = new HashMap<String,String>();
		header.put("REG_MSG_TYPE","1");              //内部指令 获取配置信息
		header.put("REG_SRC_CLIENT",loopServerId);   //标记源头服务器
		header.put("REG_SENDER",cluster.name());     //标记调用者服务器
		header.put("REG_LEVEL","1");    
		header.put("REG_OBJ_CLIENT",siVO.name); //设置目标服务器主键
		try {
			//获取配置信息
			IViewHandler res = cluster.postRvh(siVO,actionName,null,0,header);
			if(res==null || res.a("sn").length()<1) {
				//这个目标系统也没有获取到配置信息
				return;
			}
			/*
			if(boo(header.get("IFR_RECIVE_STATUS")) && res!=null && !res.isEmpty()) {
				setConfigure(res,null); //设置配置信息
				configLoaded = true;
			}
			 */
		}catch(Exception e) {}
	}
	//#endregion

	//#region validReceived(siVO) 【事件】由ClusterFilter调用，首次完成接收目标心跳后执行该方法 （无用）
	/**
	 * 【事件】由ClusterFilter调用，首次完成接收目标心跳后执行该方法 （无用）
	 * @param siVO   发送方服务器信息类
	 * 2021年03月10日
	 * @author MBG
	 * @deprecated 无用
	 */
	@Override
	public void validReceived(ServerInfoVO siVO) {}
	//#endregion
	
	//#region triggerKey() 返回触发响应主键
	/**
	 * 返回触发响应主键
	 * @return 触发响应主键
	 * 2021年03月10日
	 * @author MBG
	 */
	@Override
	public String triggerKey() {
		return "_reg_";
	}
	//#endregion

	//#region triggerRecive(siVO,req,resp) 响应发起服务器请求
	/**
	 * 响应发起服务器请求
	 * @param siVO   发起服务器信息类
	 * @param req    请求对象
	 * @param resp   反馈对象
	 * 2019年10月8日
	 * @author MBG
	 */
	@Override
	public void triggerRecive(ServerInfoVO siVO, IRequest req, IResponse resp) {}
	//#endregion
	
	//#region getIndex() 获取排序索引，数值越小越先执行
	/**
	 * 获取排序索引，数值越小越先执行
	 * @author MBG
	 * 2021-03-10 16:41
	 * @return 排序索引
	 */
	@Override
	public int getIndex() {
		//注意：该值一定要比ConsoleFilter中的索引值大，否则会不认加密后的动作路径
		return 60;
	}
	//#endregion

	//#region getFilterActionExtName() 获取需要过滤的动作路径扩展名
	/**
	 * 获取需要过滤的动作路径扩展名
	 * @return 扩展名
	 * 2021-03-10 16:41
	 * @author MBG
	 */
	@Override
	public String getFilterActionExtName() {
		return "*";
	}
	//#endregion

	//#region doFilter(req,resp) 执行过滤
	/**
	 * 执行过滤
	 * 刘虻
	 * 2021-03-10 16:41
	 * @param req        页面请求
	 * @param resp       页面反馈
	 * @return           如果返回真，则不继续往下进行，直接跳过结束
	 * @throws Exception 执行发生异常
	 */
	@Override
	public boolean doFilter(IRequestManager req, IResponseManager resp) throws Exception {
		if(!enabledBeanReg) {
			return false;
		}
		//获取动作路径，不带虚拟路径
		String servletPath = req.getServletPath();
		if(servletPath.equals(actionName)) {
			//TODO 内部请求
			return true;
		}
		if(cluster!=null && 
				!cluster.disabled() && 
				cluster.callFromCluster(req) && 
				str(req.getHeader("_cluster_call_")).length()>0) {
			//在线开发，代理入口
			return false;
		}
		String subPath    = null; //触发相对路径
		String subServlet = null; //除去一级路径的后续路径
		int point         = servletPath.indexOf("/",1); //获取一级路径作为触发相对路径
		if(point>0) {
			subPath     = servletPath.substring(0,point);
			subServlet  = servletPath.substring(point);
		}else {
			subPath     = "";
			subServlet  = servletPath;
		}
		ActionVO avo; //动作信息类
		long     now; //当前时间
		if(cluster==null || cluster.disabled() || localContextVO!=null && subPath.equals(localContextPath)){
			//本地请求
			avo = localContextVO.actionMap.get(subServlet);
			if(avo==null){
				return false;
			}
			now = System.currentTimeMillis(); //当前时间
			recordAction(avo,now); //在调用动作前，记录相关请求信息
			try{
				callLocal(avo,req,resp); //调用本地动作
			}catch(Throwable e){
				e.printStackTrace();
				recordActionError(avo,e); //记录出错信息
			}finally{
				recordActionFinall(avo,now); //记录动作结束时的信息
			}
			return true;
		}
		//获取远程上下文信息类
		ContextVO rctxVO = ctxMap.get(subPath);
		if(rctxVO==null){
			return false;
		}
		if(rctxVO.needProxy){
			//TODO  代理全部请求

			return true;
		}
		avo = rctxVO.actionMap.get(subServlet);
		if(avo!=null){
			return true;
		}
		now = System.currentTimeMillis(); //当前时间
		recordAction(avo,now); //在调用动作前，记录相关请求信息
		try{
			callRemote(avo,req,resp); //远程调用动作
		}catch(Throwable e){
			e.printStackTrace();
			recordActionError(avo,e); //记录出错信息
		}finally{
			recordActionFinall(avo,now); //记录动作结束时的信息
		}
		return false;
	}
	//#endregion

	//#region recordActionFinall(avo,now) 记录动作结束时的信息
	/**
	 * 记录动作结束时的信息
	 * @param avo 动作信息类
	 * @param now 开始时间
	 */
	private void recordActionFinall(ActionVO avo,long now){
		avo.lastInvokeUseTime = System.currentTimeMillis()-now;
	}
	//#endregion

	//#region recordActionError(avo,e) 记录动作出错信息
	/**
	 * 记录动作出错信息
	 * @param avo 动作信息类
	 * @param e   异常信息类
	 */
	private void recordActionError(ActionVO avo,Throwable e){
		if(avo.errorCount>Long.MAX_VALUE){
			avo.errorCount = 1;
		}else{
			avo.errorCount++;
		}
		if(avo.errorDayCount>Long.MAX_VALUE){
			avo.errorDayCount = 1;
		}else{
			avo.errorDayCount++;
		}
		avo.lastErrorTime = System.currentTimeMillis();
		avo.lastErrorMsg = e.toString();
	}
	//#endregion

	//#region recordAction(avo,now) 在调用动作前，记录相关请求信息
	/**
	 * 在调用动作前，记录相关请求信息
	 * @param avo 动作信息类
	 * @param now 当前时间
	 */
	private void recordAction(ActionVO avo,long now){
		if(avo.invokeCount>Long.MAX_VALUE){
			avo.invokeCount = 1;
		}else{
			avo.invokeCount++;
		}
		if(avo.dayEndTime<now){
			//新的一天，设置当日结束时间
			avo.dayEndTime = (new SDate(SDate.nowDateString()+" 23:59:59 999")).getMillisecond();
			avo.invokeDayCount = 1;
			avo.errorDayCount  = 0;
		}else if(avo.invokeDayCount>Long.MAX_VALUE){
			avo.invokeDayCount = 1;
		}else{
			avo.invokeDayCount++;
		}
	}
	//#endregion

	//#region init(bf,config) 执行初始化
	/**
	 * 执行初始化
	 * @param bf         过滤器管理类
	 * @param config     Servlet配置信息类
	 * @throws Exception 异常（如果初始化发生异常，则放弃不再使用）
	 * 2021-03-10 16:41
	 * @author MBG
	 */
	@Override
	@SuppressWarnings("deprecation")
	public void init(BaseFilter bf, IFilterConfig config) throws Exception {
		this.baseFilter = bf;
		this.config     = config;
		this.sl         = (ScriptLoader)((IBeanFactoryManager)_beanFactory).getNativeObject(ScriptLoader.class,this);
		if(localContextPath==null){
			localContextPath = "";
		}
	}
	//#endregion

	//#region callLocal(avo,req,resp) 调用本地动作
	/**
	 * 调用本地动作
	 * @param avo   动作信息类
	 * @param req   请求对象
	 * @param resp  返回对象
	 * @throws Exception 异常
	 */
	private void callLocal(ActionVO avo,IRequest req,IResponse resp) throws Exception {
		if(avo.scriptId==null && avo.scriptId.length()<1){
			resp.sendError(IResponse.SC_NOT_FOUND,
					"The Action ["+avo.actionPath+"] Not Found The ScriptID");
			return;
		}
		//动作上下文
		IActionContext ac = new ActionContext(
				 req,resp,req.getServerName().toLowerCase()
				,avo.actionPath,uploadPath());
		//清除缓存
		ac.getResponse().setHeader("Pragma","No-cache");
		ac.getResponse().setHeader("Cache-Control","no-cache");
		ac.getResponse().setDateHeader("Expires", 0);
		//解决跨域访问session丢失问题
		ac.getResponse().setHeader("P3P","CP=CAO PSA OUR");
		String[] vals = getAllowOriginVals(); //获取允许跨站域名数组
		for(int i=0;i<vals.length;i++) {
			ac.getResponse().setHeader("Access-Control-Allow-Origin",vals[i]); 
		}
		//获取线程会话
		ThreadSession.put("_action_context",ac);            //放入动作上下文
		
		if(baseFilter.isTokenMode()) {
			ThreadSession.put("_session_id",ac.getRequest().getHeader(baseFilter.getTokenKey()));
		}else {
			ThreadSession.put("_session_id",ac.getSessionID()); //将会话主键也放入线程会话
		}
		
		
		ThreadSession.put("_action_ip",ac.cip());           //将请求IP地址放入线程会话
		ThreadSession.put("_action_name",avo.actionPath);              //将动作路径放入线程会话
		invokeScriptAction(avo,ac); //调用指定脚本
	}
	//#endregion

	//#region invokeScriptAction(avo,ac) 调用本地动作脚本
	/**
	 * 调用本地动作脚本
	 * @param avo 动作信息类
	 * @param ac  动作上下文
	 * @throws Exception 异常
	 */
	public void invokeScriptAction(ActionVO avo,IActionContext ac) throws Exception {
		Map<String,String> initParaMap = null; //初始参数容器
		if(avo.sParameter!=null && avo.sParameter.length()>0){
			initParaMap = StringUtil.fixQueryString(avo.sParameter);
		}
		invokeScriptAction(avo.actionPath,avo.scriptId,ac,initParaMap);
	}
	//#endregion

	//#region getActionScript(actionPath,scriptId,ac,extParas) 获取动作类（获取过程异常信息直接输出到界面，不用再做处理）
	/**
	 * 获取动作类（获取过程异常信息直接输出到界面，不用再做处理）
	 * @param actionPath  动作路径（用于输出日志）
	 * @param scriptId    脚本主键
	 * @param ac          动作上下文
	 * @param extParas    扩展参数 0是否输出日志（该值需要返回传出）
	 * @return            脚本动作类实例
	 * @throws Exception  异常
	 */
	public IActionBean getActionScript(
			String actionPath,
			String scriptId,
			IActionContext ac,
			Object[] extParas) throws Exception {
		//获取对应的脚本信息类
		ScriptVO sVO = sl.getScriptInfo(scriptId,false);
		if(sVO==null) {
			ac.getResponse().sendError(IResponse.SC_NOT_FOUND,
					"Not Find The Script:["+scriptId+"] By Action:["+actionPath+"]");
			warning("没有获取到脚本动作类:["+scriptId+"]");
			return null;
		}
		if(sVO.clusterCallOnly) {
			if(cluster!=null && !cluster.allow(ac.getRequest())) {
				ac.getResponse().sendError(IResponse.SC_FORBIDDEN,"This Action:["+
						actionPath+"] Only cluster server calls are allowed",ac.getRequest());
				warning("收到集群外的请求:["+actionPath+"]，已经被禁止  客户端IP:["+ac.getRequest().cip()+"]");
				return null;
			}
		}
		if(sVO.actionSpecialCode) {
			//设置允许提交特殊代码
			ac.getRequest().setDisabledWrapper(true);
		}
		//获取脚本类
		Object sObj = sl.getScript(sVO);
		if(sObj==null) {
			warning("遇到意外，获取脚本:["+scriptId+"]类实例为空");
			ac.getResponse().sendError(IResponse.SC_NOT_FOUND,"Not Find The Script:["+
					scriptId+"] Instance By Action:["+actionPath+"]",ac.getRequest());
			return null;
		}
		if(sObj instanceof IActionBean) {
			if(outLog() && sVO.noOutLog) {
				if(extParas!=null && extParas.length>0){
					extParas[0] = true;
				}
				outLog(false);
			}
			((IActionBean)sObj).setActionContext(ac);
			//常规动作脚本
			log("LoadScript:["+sVO.id+"] ["+sVO.title+"] Source:["+ac.getRequest().getRemoteAddr()+"]");
			ThreadSession.put("_action_ invoker_",sVO.id); //将调用者名字放入线程会话中
		}else{
			warning("遇到意外，该脚本:["+scriptId+"]类实例并非动作类实例，动作路径：["+actionPath+"]");
			return null;
		}
		return (IActionBean)sObj;
	}
	//#endregion

	//#region executeAction(action,ab,ac,initParaMap) 执行动作脚本
	/**
	 * 执行动作脚本
	 * @param action      动作路径
	 * @param ab          脚本动作类实例
	 * @param ac          动作上下文
	 * @param initParaMap 初始参数容器
	 * @throws Exception  异常
	 */
	public void executeAction(
			String         action,
			IActionBean    ab,
			IActionContext ac,
			Map<String,?>  initParaMap) throws Exception {
		//整理传入参数 这么做主要是能让beforeRunAction方法获取到即将传入的参数
		//有些参数只能获取一次，比如XML对象或Json对象，所以提前先取好，然后用在多个方法中
		ab.buildActionParameter(initParaMap);
		if(!ab.beforeRunAction()) {
			return;
		}
		try{
			//调用动作
			ab.invokeAction();
		}catch(FixException e){
			//已经做好处理，无需再抛异常出去
		}
		ab.afterRunAction();
	}
	//#endregion

	//#region callRemote(avo,req,resp) 远程调用该动作
	/**
	 * 远程调用该动作
	 * @param avo   动作信息类
	 * @param req   请求对象
	 * @param resp  返回对象
	 */
	private void callRemote(ActionVO avo,IRequest req,IResponse resp){
		//FIXME
	}
	//#endregion

	//#region getActionVO(actionPath) 通过请求url（包含一级虚拟路径）返回动作信息类
	/**
	 * 通过请求url（包含一级虚拟路径）返回动作信息类
	 * @param actionPath 请求url（包含一级虚拟路径）
	 * @return 对应的动作信息类
	 */
	public ActionVO getActionVO(String actionPath){
		if(actionPath==null){
			return null;
		}
		//分隔符位置
		int point = actionPath.indexOf("?");
		if(point>0){
			actionPath = actionPath.substring(0,point);
		}
		if(!actionPath.startsWith("/")){
			actionPath = "/"+actionPath;
		}
		point = actionPath.indexOf("/",1);
		if(point<0){
			return getActionVO(null,actionPath);
		}
		return getActionVO(actionPath.substring(0,point),actionPath.substring(point));
	}
	//#endregion

	//#region getActionVO(contextPath,actionPath) 获取对应的动作信息类
	/**
	 * 获取对应的动作信息类
	 * @param contextPath 一级虚拟路径
	 * @param actionPath  除去一级虚拟路径的动作路径
	 * @return 对应的动作信息类
	 */
	public ActionVO getActionVO(String contextPath,String actionPath){
		//通过虚拟路径
		ContextVO ctxVO = ctxMap.get(contextPath);
		if(ctxVO==null){
			return null;
		}
		return ctxVO.actionMap.get(actionPath);
	}
	//#endregion

	//#region getAllowOriginVals() 处理 Access-Control-Allow-Origin
	/**
	 * 处理 Access-Control-Allow-Origin
	 * @return 允许跨站域名
	 * 2019年6月28日
	 * @author MBG
	 */
	private String[] getAllowOriginVals() {
		if(allowOriginVals==null) {
			//获取允许跨站域名信息
			String val = p("servlet_access_control_allow_origin");
			if(val.length()<1) {
				allowOriginVals = new String[0];
			}else {
				allowOriginVals = BaseUtil.split(val,",",";","，","；");
			}
		}
		return allowOriginVals;
	}
	//#endregion

	//#region invokeScriptAction(scriptId,req,resp) 执行脚本动作
	/**
	 * 执行脚本动作
	 * @param scriptId       脚本主键
	 * @param req            动作请求
	 * @param resp           执行反馈
	 * @throws Exception     异常
	 * 2019年12月10日
	 * @author MBG
	 */
	public void invokeScriptAction(
			String scriptId,
			IRequest req,
			IResponse resp) throws Exception {
		invokeScriptAction(scriptId,req,resp,null,null);
	}
	//#endregion

	//#region invokeScriptAction(scriptId,req,resp,domain,action) 执行脚本动作
	/**
	 * 执行脚本动作
	 * @param scriptId       脚本主键
	 * @param req            动作请求
	 * @param resp           执行反馈
	 * @param domain         当前访问过来的域名
	 * @param action         动作名
	 * @param uploadBasePath 文件上传根路径
	 * @throws Exception     异常
	 * 2019年12月4日
	 * @author MBG
	 */
	public void invokeScriptAction(
			String    scriptId,
			IRequest  req,
			IResponse resp,
			String    domain,
			String    action) throws Exception {
		invokeScriptAction(scriptId,scriptId,new ActionContext(req,resp,domain,action,uploadPath()),null);
	}
	//#endregion

	//#region invokeScriptAction(action,scriptId,ac,initParaMap) 执行脚本动作
	/**
	 * 执行脚本动作
	 * @param action      动作路径
	 * @param scriptId    脚本主键
	 * @param ac          动作上下文
	 * @param initParaMap 初始参数容器
	 * @throws Exception  异常
	 */
	public void invokeScriptAction(
			String         action,
			String         scriptId,
			IActionContext ac,
			Map<String,?>  initParaMap) throws Exception {
		String queryStr = ac.getRequest().getQueryString(); //url请求参数
		boolean needOutLog = false; //执行后是否允许输出日志
		if(outLog() && queryStr!=null && queryStr.indexOf("_nolog_")>0){
			outLog(false);
			needOutLog = true;
		}
		//获取动作对应的方法名
		Object time = log.runBefore(); //标记执行开始时间
		try{
			Object[] extParas = new Object[1];
			extParas[0] = needOutLog;
			//强制转换为动作类
			IActionBean ab = getActionScript(action,scriptId,ac,extParas);
			if(ab==null){
				return;
			}
			needOutLog = (boolean)extParas[0];
			ab.setActionContext(ac); //设置动作上下文
			executeAction(action,ab,ac,initParaMap); //执行动作脚本
			if(ab.isNoReturnInfo()){
				return;
			}
			IViewHandler vh = null; //页面对象
			Json   reJson   = null; //即将输出的Json对象
			String encoding = null; //输出编码
			switch(ab.getOutContentType()) {
			case 0:
				vh = ab.beforeOutView(ab.getReVh());
				encoding = vh.getDealEncode();
				if(encoding==null || encoding.length()<1) {
					encoding = "UTF-8";
				}
				ac.getResponse().setHeader("Content-Type","text/html; charset="+encoding);
				break;
			case 1:
				vh = ab.beforeOutView(ab.getReXml());
				if(vh!=null) {
					encoding = vh.getDealEncode();
					if(encoding==null || encoding.length()<1) {
						encoding = "UTF-8";
					}
				}else {
					encoding = "UTF-8";
				}
				ac.getResponse().setHeader("Content-Type","text/xml; charset="+encoding);
				break;
			default:
				reJson = ab.beforeOutJson(ab.getReJson());
				ac.getResponse().setHeader("Content-Type","application/json; charset=UTF-8");
				break;
			}
			boolean noOut = true; //是否没输出数据
			if(reJson!=null && !reJson.isEmpty()) {
				/* jsonp 支持方法
				 * 如果存在这个参数值，则采用jsonp的返回格式
				 * 即：方法名(json返回内容);
				 * 
				 * 方法名是从request中，获取的  request.getParameter("jsoncallback");
				 * 
				 * jsoncallback 是写死的，目前还没有改变的必要
				 * 
				 */
				String jsonCallBack = ac.getParameter("jsoncallback");

				//输出流
				PrintWriter writer = ac.getResponse().getWriter();
				if(jsonCallBack.length()>0) {
					debug("===============Jsonp Mode:[YES]===============");
					debug(reJson.toString());
					debug("(=======Return=======)");
					writer.write(jsonCallBack+"(");
					writer.write(reJson.toString());
					writer.write(");");
				}else {
					debug(reJson.toString());
					debug("(=======Return=======)");
					writer.write(reJson.toString());
				}
				noOut = false;
			}
			if(noOut && vh!=null) {
				//之前遇到个无法呈现的问题，偶尔输出内容全是问号
				//原来使用的是vh.getDealEncode();这回写死UTF-8
				//基本输出编码 weblogic只能用基本编码输出
				String standardOutEncoding = "UTF-8";
				try {
					//设置编码格式
					ac.getResponse().setCharacterEncoding(standardOutEncoding);
				}catch(NoSuchMethodError e) {
					//使用WebLogic时会抛异常到这里，然后使用标准输出
					//非weblogic不能使用标准输出，否则反而出现乱码
					standardOutEncoding = "ISO-8859-1";
				}
				try {
					//输出信息到页面
					vh.getNodeBody(
							new PrintWriterTool(
									ac.getResponse().getWriter(),
									vh.getDealEncode(),
									standardOutEncoding,
									false));
					//不能输出html代码到页面，太乱
				}catch(Exception e) {}
			}
		}finally{
			log.writeRuntime(time,"Execute Complete Local ActionScript:["+scriptId+"] Action:["+action+"]");
			if(needOutLog){
				outLog(true);
			}
		}
	}
	//#endregion

	//#region regist(bean)
	/**
	 * 覆盖方法
	 */
	@Override
	public boolean regist(Object bean) {
		if(!enabledBeanReg){
			return true;
		}
		if(!(bean instanceof IActionBean)) {
			return false;
		}
		//脚本主键
		String scriptId = str(((IScriptBean)bean).getScriptId()).toUpperCase();
		//动作路径
		String action = "/"+scriptId+"."+localActionExtName;
		//获取该脚本对应的脚本信息类
		ScriptVO sVO = ((IScriptBean)bean).getScriptVO();
		//本地上下文信息类
		if(localContextVO==null){
			localContextVO = new ContextVO();
			localContextVO.localReg    = true;
			localContextVO.contextPath = localContextPath;
			ctxMap.put(localContextPath,localContextVO);
		}
		//获取该动作信息类
		ActionVO avo = localContextVO.actionMap.get(action);
		if(avo==null){
			avo = new ActionVO();
			avo.actionPath = action;
			localContextVO.actionMap.put(action,avo);
		}else{
			warning("重复注册动作类，动作路径:["+
					action+"] 已注册脚本：["+avo.scriptId+
					"] 随后注册脚本：["+scriptId+"]");
		}
		avo.contextPath   = "";
		avo.groupId       = "_local_";
		avo.title         = sVO.title;
		avo.scriptId      = scriptId;
		avo.isLocalServer = true;
		if(!disabledCluster && cluster!=null){
			avo.clusterGroupId  = cluster.group();
			avo.clusterServerId = cluster.name();
		}
		if(bean instanceof IBeanRegisterChild) {
			((IBeanRegisterChild)bean).setRegister(this);
			((IBeanRegisterChild)bean).afterRegister();
		}
		log.startLog("---Regc:["+sVO.id+" "+sVO.title+"] action:["+action+"]");
		return true;
	}
	//#endregion

	//#region unRegist(bean)
	/**
	 * 覆盖方法
	 */
	@Override
	public void unRegist(Object bean) {
		if(!enabledBeanReg){
			return;
		}
		if(!(bean instanceof IActionBean)) {
			return;
		}
		if(localContextVO==null){
			return;
		}
		if(bean instanceof IBeanRegisterChild) {
			((IBeanRegisterChild)bean).beforeUnRegister();
		}
		//移除动作信息
		localContextVO.actionMap.remove("/"+str(((IScriptBean)bean).getScriptId()).toUpperCase()+"."+localActionExtName);
	}
	//#endregion

	//#region destroy()
	/**
	 * 覆盖方法
	 */
	@Override
	public void destroy() {}
	//#endregion
}







